#include <stdio.h>
#include<stdlib.h>
#include <assert.h>
#include <string.h>
#include <unistd.h>
#include<sys/types.h>
#include <sys/wait.h>

#define LEFT "["
#define RIGHT "]"
#define LABLE "#"
#define DELIM " \t"
#define LINE_SIZE 1024
#define EXIT_CODE 44
#define ARGC_SIZE 32//允许最多输入32个子串 
int quit=0;
int lastcode=0;
extern char** environ;
char commandline[LINE_SIZE];
char* argv[ARGC_SIZE];
char pwd[LINE_SIZE];

//自定义环境变量表
char myenv[LINE_SIZE];
//自定义本地变量表，属于shell内部的变量，这里不在做了
const char* getusername()
{
  return getenv("USER");
}

const char* gethostname()
{
  return getenv("HOSTNAME");
}

//const char* getpwd()
//{
 //return getenv("PWD");
//}

void getpwd()
{
  getcwd(pwd,sizeof(pwd));
}

void Interact(char* cline,int size)
{
  getpwd(); 
  printf(LEFT"%s@%s %s"RIGHT""LABLE"",getusername(),gethostname(),pwd);
  //scanf("%s",commandline);
  char* s=fgets(cline,size,stdin);
  assert(s);//assert只在debug下起作用，release下被删掉；只要读到内容s就不会为空，但是不排查外设的问题
  (void)s;//消除编译器对于变量不使用的警告

  //"abcd\n\0"
  cline[strlen(cline)-1]='\0';//覆盖一定会输入进去的\n；直接回车则会被直接改成字符0，输出的也就是空字符串
}

int splitstring(char cline[],char* _argv[])
{
  int i=0; 
  if(cline[0]=='\0') return 0; //处理只输入回车的情况
  _argv[i++]=strtok(cline,DELIM);
  while(_argv[i++]=strtok(NULL,DELIM));//拆分子串存放到argv中去，同时将让argv的最后一个参数设为了NULL  
  return i-1;//argv的最后是空，要减去空
}

void NormalExcute(char* _argv[])
{
     pid_t id=fork();
     if(id<0){
        perror("fork");
        return;
      }
     else if(id==0){
        //让子进程执行命令
        //execvpe(_argv[0],_argv,environ);//传不传环境变量都可以，子进程会继承
        execvp(_argv[0],_argv);//我们已经有了参数的数组，并且在我们shell中输入的命令需要让它去环境变量中找然后执行，所以使用带vp的接口
        exit(EXIT_CODE);
      }
     else{
          int status=0;
          pid_t rid=waitpid(id,&status,0);
          if(rid==id){
              lastcode=WEXITSTATUS(status);
            }
         }
}

int buildCommand(char* _argv[],int _argc)
{
     if(_argc==2&&strcmp(_argv[0],"cd")==0)
     {
        chdir(argv[1]);
        getpwd();
        sprintf(getenv("PWD"),"%s",pwd);//自己的shell的pwd环境变量不会因为修改路径而变，所以需要修改一下；可以使用putenv修改，这里改的调用了getcwd 
        return 1;
     }
     else if(_argc==2&&strcmp(_argv[0],"export")==0) 
     {
       //这里putenv导入的环境变量是存放在arg里的指针指向的空间中的，但是我们的argv里的指针是一直在变的，所以环境变量就没了
       strcpy(myenv,_argv[1]);
       putenv(myenv);//这样在我们的shell执行我们的程序的时候这个环境变量就会继承给这个程序也就是子进程
       return 1;
     }
     else if(_argc==2&&strcmp(_argv[0],"echo")==0)
     {
       if(strcmp(_argv[1],"$?")==0)
       {
         printf("%d\n",lastcode);
         lastcode=0;
       }
       else if(*_argv[1]=='$')
       {
         char* val=getenv(_argv[1]+1);
         if(val) printf("%s\n",val);
       }
       else
       {
         printf("%s\n",_argv[1]);//如果输入的字符串带引号应该去掉，这里就不写了，情况有点多了
       }
       return 1;
     }

     //使ls命令带颜色
     if(strcmp(_argv[0],"ls")==0)
     {
       _argv[_argc++]="--color";
       _argv[_argc]=NULL;
     }
     return 0;
}
int main()
{ 
   //shell本身是死循环，我们的shell只要进程不退出，就能一直输入命令
   while(!quit){
        //1.
        
        //2.交互问题，获取命令行
        Interact(commandline,sizeof(commandline));
        //printf("echo:%s\n",commandline);
       
        //3.子串分割的问题，解析命令行
        //commandline-> "ls -a -l -n\0"-> "ls" "-a" "-l" "-n"
        int argc=splitstring(commandline,argv);
        //printf("%d\n",argc);//输入回车这里居然是1？？？那不就不走continue了？
        //break;
        if(argc==0) continue;

        //debug``
        //for(int i=0;argv[i];i++) printf("[%d]:%s\n",i,argv[i]);
        

        //4.指令的判断
        //内键命令，本质就是一个shell内部的一个函数
        int n=buildCommand(argv,argc);


        //5.普通命令的执行
        if(!n)
        NormalExcute(argv);
   }
   return 0;
}
